<?php
/**
 * Class Page created by Havriniuc Serghei<br>
 * Date: 01.08.12<br>
 * Time: 14:46<br>
 * This class is a superclass to use for subclassing page controllers.
 * The logic of this class is that you extend it for every new controller class. Every controller has <b>actions,</b>
 * that is, some specific separated blocks of logic. Those <b>actions</b> are executed by action functions, that are simply
 * non-static methods whose names end in <b>Action</b>(case-sensitive), i.e. <code>function addEditAction()</code> The actions
 * can be called from front-end by passing a GET or POST variable <b>action,</b> that contains the name of the action. Please
 * note that this name must not end in <b>Action.</b> That is, passing <code>'&action=my'</code> will trigger the
 * <code>myAction()</code> function. Passing <code>'&action=addEdit'</code> will trigger the <code>addEditAction()</code>
 * function an so on. If no 'action' variable is passed, or the action specified by that variable is a wrong one, the
 * <code>defaultAction()</code> function will be invoked. Thus, your controller must always have a <code>defaultAction</code>
 * function. An action function must always return an array that indicates what data it sends back. Please see
 * processResult for more information on this topic
 * This is a cornerstone class that provides the Controller(C) functionality for our implementation of MVC
 * @see http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
 * @see \Page::processResult()
 * @see \Page_Be_CMS_Pages
 */

class Page extends Engine {
	/**
	 * @var array $actionResult The result of the last invoked action. After the action finished, this result is processed
	 * by the processResult() function
	 * @see \Pages::processResult()
	 */
	private $actionResult;

	/**
	 * @var \Common Contains an instance of a Common subclass
	 */
	protected $tableObject;

	/**
	 * @var array Contains the names of the actions which need the $this->tableObject to be instantiated. Please specify
	 * the complete names of the action function, i.e. array('saveAction', 'addEditAction'). Please double check
	 * if you really need the DB object to be instantiated for your action, to avoid unnecessary queries.
	 */
	protected $actionsTableObject = array();

	/**
	 * @var string The class name of a model class to be used for $this->tableObject instantiation. Normally should be a
	 * subclass of \Common class
	 */
	protected $classTableObject;

	/**
	 * @var string The value of tableObject's entry primary key. If a bad value is supplied for this field and tableObject
	 * fails to instantiate, the execution will be aborted and an error will be shown to the user
	 * @see \Page::initTableObject()
	 */
	protected $idTableObject;

	/**
	 * @var bool If set to true and $this->message is not empty, an error box will be shown in front-end
	 * @see $this->message
	 */
	protected $error = false;

	/**
	 * @var string Allows the showing of a message box to the user. If $this->error is set to true, the box will be styled
	 * as an error box
	 * @see $this->error
	 */
	protected $message;

	/**
	 * @var array An array containing the breadcrumbs
	 */
	protected $breadcrumbs = array();


	/**
	 * Processes the action, calls the according action function and instantiates the tableObject if necessary. Normally
	 * you would override this constructor in your subclass, but please don't forget to call <code>parent::__construct()</code>
	 * at the end of it.
	 * The action variable can be passed either in POST or GET, but the POST value is prioritized over the one from GET.
	 * @uses \Page::initTableObject()
	 * @uses \Page::processResult()
	 * @see \Page_Be_CMS_Pages::__construct()
	 */
	function __construct() {
		/**
		 * This function mainly includes JS and CSS for the "in page translations"
		 */
		self::$ACTION = (post('action', 'string') ? post('action', 'string') : get('action', 'string')) . 'Action';

		//check permission
		if (isset(self::$PAGE['pm_identifier']) && self::$PAGE['pm_identifier']) {
			perm(self::$PAGE['pm_identifier'], true);
		}

        if(self::$ACTION && !method_exists($this, self::$ACTION)) {
            self::$ACTION = DEFAULT_CONTROLLER_ACTION;
        }

        if(method_exists($this, self::$ACTION)) {
            $this->initTableObject();
            $this->actionResult = call_user_func(array($this, self::$ACTION));
        } else {
            //call 404 page
            //@todo we must send to page 404 kind of error and display when debug is true
			Db_Cms_Pages::get404Page();
            self::$CONTROLLER = new self::$PAGE['p_controller'];
        }
		$this->processResult();
	}

	/**
	 * Processes the result(the return) of the last executed action, handles the headers, redirects etc. The said result
	 * must be an associative array(a hash), having a <b>type</b> key. This key can have one of the values:
	 * <code>
	 * 'display'---Send HTML to browser to display. In this case, you can pass a 'content' key, holding the name(without extension, i.e., to display 'add_edit.phtml' pass 'add_edit') of the template(View) you'd like to display. If skipped, ->display('', true) will be invoked on the template, meaning you have to set a body template before exiting the action function
	 * </code>
	 * <code>
	 * 'redirect'---Redirects the browser to specific location. When using this value you can have on of the two keys:
	 * 		'content'---Holding the url to redirect to
	 * 		'query'---If not null, all the GET parameters shall be removed from the URL. Usually this is used to redirect to the default action
	 * </code>
	 * <code>
	 * 'json'---When adding a 'content' key to the action result, and setting a value to it, the function will call json_encode on this value and return json
	 * </code>
	 * <code>
	 * 'encoded_json'---Use this value instead of 'json' when you want to pass a JSON encoded string for the 'content' key
	 * </code>
	 * <code>
	 * 'xml'---Use this value and assign an XML string to the the 'content' key to output XML
	 * </code>
	 * <code>
	 * 'string'---Use this value and assign a string value to the 'content' key to output a string
	 * </code>
	 * @see \TemplateParser::setBodyTemplate($bodyFileName)
	 * @uses \TemplateParser::display($tplName,$returnContent)
	 * @uses \Utils::redirectTo($url,$removeQuery)
	 */
	private function processResult() {
        $output = '';
		if (is_array($this->actionResult) && isset($this->actionResult['type'])) {
			if (isset($this->actionResult['headers']) && is_array($this->actionResult['headers'])) {
				foreach ($this->actionResult['headers'] as $headerString) {
					header($headerString);
				}
			}

            $allowedTypesForCache = array ('display', 'json', 'xml');
			switch ($this->actionResult['type']) {
				case 'display': {
					if (PROJECT == 'admin' && !self::$PAGE['p_is_index']) {
						$this->initBreadcrumbs();
					}
					if (isset($this->actionResult['content'])) {
						if (conf('DEBUG') == true) {
							$result = self::$TPL->display($this->actionResult['content'], true);
							$debug = self::$DEBUG->displayDebug(Utils::endTimer());
							$output = str_replace(DEBUG_PLACEHOLDER, $debug, $result);
						} else {
							$output = self::$TPL->display($this->actionResult['content'], true);
						}
					} else {
						if (conf('DEBUG') == true) {
							$result = self::$TPL->display('', true);
							$debug = self::$DEBUG->displayDebug(Utils::endTimer());
							$output = str_replace(DEBUG_PLACEHOLDER, $debug, $result);
						} else {
							$output = self::$TPL->display('', true);
						}
					}
					break;
				}
				case 'redirect': {
					if (!empty($this->message)) {
						$this->setUserMessage();
					}
					if (isset($this->actionResult['content'])) {
						Utils::redirectTo($this->actionResult['content']);
					} else {
						Utils::redirectTo(false, isset($this->actionResult['query']) ? !$this->actionResult['query'] : false);
					}
					break;
				}
				case 'json': {
					if (isset($this->actionResult['content'])) {
						header('Content-type: application/json');
						$output = json_encode($this->actionResult['content']);
					}
					break;
				}
				case 'encoded_json': {
					if (isset($this->actionResult['content'])) {
						header('Content-type: application/json');
						$output = $this->actionResult['content'];
					}
					break;
				}
				case 'xml': {
					if (isset($this->actionResult['content'])) {
						header('Content-type: text/xml');
						$output = Utils::arrayToXml($this->actionResult['content']);
					}
					break;
				}
				case 'string': {
					if (isset($this->actionResult['content'])) {
						$output = $this->actionResult['content'];
					}
					break;
				}
				default: {

					break;
				}


			}

            if (
                isset($this->actionResult['cache']) &&
                $this->actionResult['cache'] === true &&
//                conf('DEBUG') == false &&
                in_array($this->actionResult['type'], $allowedTypesForCache)
            ) {
                Cache::set($output);
            }

            print $output;

		}
	}

	/**
	 * Returns a json encoded array of arguments
	 * @used-by \Page::moveUpDownAjax($btnAction,$tableObject,$result=array())
	 * @param bool $error Optional. Defaults to false
	 * @param string $message Optional. Defaults to an empty string
	 * @param mixed $result Optional. Defaults to an empty array
	 * @return array
	 */
	protected function jsonResult($error = false, $message = '', $result = array()) {
		return json_encode(array(
			'error' => $error,
			'msg' => $message,
			'result' => $result
		));
	}

	/**
	 * Initializes the table object. This function will instantiate <code>$this->tableObject</code> with an instance
	 * of the class specified in the <code>$this->classTableObject</code> with the primary key specified in <code>
	 * $this->idTableObject</code>. If a bad value is supplied for this field and tableObject fails to instantiate, the
	 * execution will be aborted and an error will be shown to the user
	 * @uses $this->idTableObject
	 * @uses $this->tableObject
	 */
	protected function initTableObject() {
		if (is_array($this->actionsTableObject) && in_array(self::$ACTION, $this->actionsTableObject)) {
			$this->tableObject = new $this->classTableObject($this->idTableObject);
			if ($this->idTableObject && !$this->tableObject->objectArray->id) {
				$this->error = true;
				$this->message = t(T_ERROR_MESSAGE, 'item_not_exists');
				$this->setUserMessage();
				Utils::redirectTo(false, true);
			}
		}
	}

	/**
	 * Sets a message in session.
	 * @uses $this->message
	 * @uses $this->error
	 * @see \Session::message($message,$error)
	 */
	protected function setUserMessage() {
		self::$S->message($this->message, $this->error);
	}

	/**
	 * A function that allows the sorting in the DB of the entries that have a position field
	 * @uses \Common::positionMoveUp()
	 * @uses \Common::positionMoveDown()
	 * @uses \Pages::jsonResult($error,$message,$result)
	 * @param string $btnAction An 'up' or 'down'(case-sensitive) string. Will show an error to the user if the string
	 * does not equal to 'up' nor 'down'
	 * @param \Common $tableObject An instance of the \Common subclass that contains the realization of positionMoveUp() and
	 * positionMoveDown() methods
	 * @param array $result Additional data to be returned by json. Optional. Defaults to an empty array
	 * @return array|bool False if $tableObject is not a subclass of Common or if the value of $btnAction is not one of
	 * 'up' or 'down'. If the sorting/changing of the position failed the function will return a processResult()-compatible
	 * array containing an error, thus sending an error message to the user. Will return true on successful position change.
	 * to the user. Will return true on success.
	 */
	protected function moveUpDownAjax($btnAction, $tableObject, $result = array()) {
		if (!($tableObject instanceof Common)) {
			return false;
		}
		if($btnAction == 'up') {
			if(!$tableObject->positionMoveUp()) {
				$message = t(T_FORM_MSG, 'move_up_msg_false');
				return array(
					'type' => 'encoded_json',
					'content' => $this->jsonResult(true, $message, $result)
				);
			}
		} else if($btnAction == 'down') {
			if(!$tableObject->positionMoveDown()) {
				$message = t(T_FORM_MSG, 'move_down_msg_false');
				return array(
					'type' => 'encoded_json',
					'content' => $this->jsonResult(true, $message, $result)
				);
			}
		} else {
			$message = t(T_FORM_MSG, 'set_move_action');
			return array(
				'type' => 'encoded_json',
				'content' => $this->jsonResult(true, $message, $result)
			);
		}
		return true;
	}

	/**
	 * Initializes the breadcrumbs, that is, adds an item to the breadcrumbs that containing the translated page title
	 * and linking to the default page URL.
	 * @used-by \Page::processResult()
	 * @uses $this->breadcrumbs
	 */
	protected function initBreadcrumbs() {
		$this->breadcrumbs['first'] = array('title' => t(T_PAGE_TITLE, self::$PAGE['p_title']), 'url' => '/' . self::$PAGE['p_url'], 'icon' => self::$PAGE['mp_item_icon']);
		self::$TPL->assign('breadcrumbs', $this->breadcrumbs);
	}

	/**
	 * Add an item to the breadcrumbs
	 * @param string $title The title of the item. Optional. Defaults to an empty string
	 * @param string $url The url of the item. Optional. Defaults to '#'. It is useful to skip this parameter when you
	 * want to bind a JS handler to the breadcrumb item
	 * @param bool $isActive Whether the item is active or not. That is, whether a special CSS class that makes it look
	 * 'active' should be assigned to it or not. Normally, you want to make the last added breadcrumb active, or, the item
	 * where the user is currently 'located'. Note that you must take of the fact that only one item is active, the class
	 * does not make any checks for you.
	 */
	protected function addBreadcrumb($title = '', $url = '#', $isActive = false) {
		if(!empty($title)) {
			$this->breadcrumbs[] = array('title' => $title, 'url' => $url, 'active' => $isActive);
		}
	}
}
